package com.pond.pond.websocket;

import com.alibaba.fastjson2.JSONObject;
import com.pond.common.core.domain.R;
import com.pond.common.core.utils.JwtUtils;
import com.pond.pond.client.SysUserClient;
import com.pond.system.api.domain.SysRole;
import com.pond.system.api.domain.SysUser;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.net.URI;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Component
@RestController
@RequestMapping("/ws")
public class WebSocketHandler extends TextWebSocketHandler {

    private static final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
    private static final Map<String, Set<String>> pondGroupMap = new ConcurrentHashMap<>();

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private SysUserClient sysUserClient;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        URI uri = session.getUri();
        if (uri == null) {
            session.close(CloseStatus.BAD_DATA);
            log.warn("连接 URI 为空，关闭连接");
            return;
        }

        List<String> authHeaders = session.getHandshakeHeaders().get("Authorization");
        String token = null;
        if (authHeaders != null && !authHeaders.isEmpty()) {
            token = authHeaders.get(0);
        }
        if (token == null) {
            String query = uri.getQuery();
            Map<String, String> params = parseQueryParams(query);
            token = params.get("token");
        }
        if (token == null) {
            List<String> cookies = session.getHandshakeHeaders().get("Cookie");
            if (cookies != null) {
                for (String cookie : cookies) {
                    for (String c : cookie.trim().split("=", 2)) {
                        String[] kv = c.trim().split("=", 2);
                        if (kv.length == 2 && "Admin-Token".equals(kv[0])) {
                            token = kv[1];
                            break;
                        }
                    }
                    if (token != null) break;
                }
            }
        }

        if (token == null) {
            session.close(CloseStatus.BAD_DATA);
            log.warn("缺少 token，连接关闭");
            return;
        }

        if (token.startsWith("Bearer ")) {
            token = token.substring(7);
        }

        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            session.close(CloseStatus.BAD_DATA);
            log.warn("token 无效，连接关闭");
            return;
        }

        Object userIdObj = claims.get("user_id");
        Long userId = null;
        if (userIdObj instanceof Integer) {
            userId = ((Integer) userIdObj).longValue();
        } else if (userIdObj instanceof Long) {
            userId = (Long) userIdObj;
        } else if (userIdObj instanceof String) {
            userId = Long.valueOf((String) userIdObj);
        }

        if (userId == null) {
            session.close(CloseStatus.BAD_DATA);
            log.warn("token 中没有 user_id，连接关闭");
            return;
        }

        final String userIdStr = userId.toString();
        sessionMap.put(userIdStr, session);
        log.info("连接建立，已放入 sessionMap，userId={}，session isOpen={}", userIdStr, session.isOpen());

        // 添加 userId 到 session 属性，方便后续取用
        session.getAttributes().put("userId", userIdStr);

        R<SysUser> r = sysUserClient.getUserById(userId);
        if (r != null && r.getData() != null) {
            SysUser user = r.getData();
            List<String> roleKeys = user.getRoles().stream()
                    .map(SysRole::getRoleKey)
                    .collect(Collectors.toList());
            List<String> adminRoles = Arrays.asList("fishingceo", "manager", "admin");
            boolean isAdmin = roleKeys.stream().anyMatch(adminRoles::contains);
            if (isAdmin) {
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignored) {}
                    WebSocketSession s = sessionMap.get(userIdStr);
                    log.info("异步发送前检查 session，userId={}，session={}", userIdStr, s);
                    if (s != null && s.isOpen()) {
                        sendUnreadMessagesToAdmin(userIdStr);
                        log.info("管理员用户[{}]连接成功，已发送未读消息", userIdStr);
                    } else {
                        log.warn("管理员用户[{}]连接成功，但session为null或关闭，未发送未读消息", userIdStr);
                    }
                });
            }
        }

        log.info("用户[{}]连接成功", userId);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("收到客户端消息：{}", message.getPayload());

        JSONObject json = JSONObject.parseObject(message.getPayload());
        String type = json.getString("type");
        String from = json.getString("from");
        String to = json.getString("to");
        String pondId = json.getString("pondId");

        switch (type) {
            case "group":
                if (StringUtils.isEmpty(pondId)) {
                    session.sendMessage(new TextMessage("错误：群聊消息必须包含 pondId"));
                    return;
                }
                // 确保发送时将消息包装为带有 read: false 的结构
                JSONObject groupMessage = new JSONObject();
                groupMessage.put("content", json); // 原始消息内容
                groupMessage.put("read", false);
                sendToGroup(pondId, groupMessage.toString());
                break;

            case "private":
                if (StringUtils.isEmpty(to)) {
                    session.sendMessage(new TextMessage("错误：私聊消息必须包含接收用户ID（to）"));
                    return;
                }
                // 确保发送时将消息包装为带有 read: false 的结构
                JSONObject privateMessage = new JSONObject();
                privateMessage.put("content", json); // 原始消息内容
                privateMessage.put("read", false);
                sendToUser(to, privateMessage.toString());
                break;

            case "admin":
                // 这里是客户端发送给服务器的admin类型消息，通常用于管理命令
                // 如果是服务器要推送admin消息给客户端，请看notifyAdmins方法
                // 当前这里不需要特殊处理，除非客户端发送的admin消息需要封装
                // 确保发送时将消息包装为带有 read: false 的结构
                JSONObject receivedAdminMessage = new JSONObject();
                receivedAdminMessage.put("content", json); // 原始消息内容
                receivedAdminMessage.put("read", false);
                // 通常客户端发送的admin消息会经过服务器处理后，再由服务器推送给其他admin
                // 所以这里直接转发或者进行其他业务逻辑处理
                // 如果需要转发给所有admin，应该调用 notifyAdmins(receivedAdminMessage.toString())
                // 否则，这里根据实际业务进行处理
                // 示例：简单地通知其他管理员
                notifyAdmins(receivedAdminMessage.toString());
                break;

            case "read":
                String msgId = json.getString("timestamp"); // 假设前端用 timestamp 作为唯一标识
                String targetUserId = (String) session.getAttributes().get("userId"); // 当前操作者，即接收方

                if (StringUtils.hasText(msgId) && StringUtils.hasText(targetUserId)) {
                    String redisKey = "admin:unread:" + targetUserId;
                    List<String> msgs = redisTemplate.opsForList().range(redisKey, 0, -1);
                    if (msgs != null) {
                        for (int i = 0; i < msgs.size(); i++) {
                            String storedMsgStr = msgs.get(i);

                            if (!StringUtils.hasText(storedMsgStr)) {
                                log.warn("Redis中发现空字符串消息，跳过处理：索引={}", i);
                                continue;
                            }

                            // **关键修改：清理字符串，去除不可见字符，再进行解析**
                            // 这个正则表达式移除了大部分控制字符，并保留换行符、制表符。
                            String cleanedMsgStr = storedMsgStr.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "").trim();

                            // 检查清理后的字符串是否以JSON对象或数组开头
                            if (!cleanedMsgStr.startsWith("{") && !cleanedMsgStr.startsWith("[")) {
                                log.warn("Redis中发现非JSON格式消息（旧格式或损坏，无法以{或[开头）：{}。该消息将无法标记为已读。", storedMsgStr);
                                continue;
                            }

                            JSONObject storedMsg = null;
                            JSONObject content = null; // 提前声明content

                            try {
                                storedMsg = JSONObject.parseObject(cleanedMsgStr); // 尝试解析清理后的字符串

                                // 确保解析成功且是期望的包含 content 的结构
                                if (storedMsg != null && storedMsg.containsKey("content")) {
                                    Object contentObj = storedMsg.get("content");
                                    if (contentObj instanceof JSONObject) { // 如果 content 字段是 JSONObject
                                        content = (JSONObject) contentObj;
                                    } else { // 如果 content 是字符串或其他类型，则无法进一步获取 timestamp
                                        log.warn("消息的content字段不是JSONObject，无法提取timestamp进行已读标记：{}。", storedMsgStr);
                                        continue;
                                    }
                                } else {
                                    log.warn("Redis中发现格式不完整或非预期消息（缺少content字段或content非JSONObject）：{}。该消息将无法标记为已读。", storedMsgStr);
                                    continue;
                                }

                                if (content != null && msgId.equals(content.getString("timestamp"))) {
                                    storedMsg.put("read", true); // 设置 read 字段为 true
                                    redisTemplate.opsForList().set(redisKey, i, storedMsg.toString());
                                    log.info("已读消息状态更新成功：msgId={}, userId={}", msgId, targetUserId);
                                    break; // 找到并更新后就可以退出循环
                                }
                            } catch (Exception e) {
                                log.warn("解析缓存消息失败或消息格式不正确（即使清理后）：{}。该消息将无法标记为已读。", storedMsgStr, e);
                                continue; // 跳过当前消息，继续处理下一条
                            }
                        }
                    } else {
                        log.warn("未找到用户[{}]的未读消息列表", targetUserId);
                    }
                } else {
                    log.warn("read 类型消息缺少 msgId 或 userId：msgId={}, userId={}", msgId, targetUserId);
                }
                break;

            // --- 新增的 delete 功能 ---
            case "delete":
                String deleteMsgId = json.getString("timestamp"); // 获取要删除的消息的 timestamp
                String deleteTargetUserId = (String) session.getAttributes().get("userId"); // 当前操作者，即删除方

                if (StringUtils.hasText(deleteMsgId) && StringUtils.hasText(deleteTargetUserId)) {
                    String redisKey = "admin:unread:" + deleteTargetUserId;
                    List<String> msgs = redisTemplate.opsForList().range(redisKey, 0, -1);
                    if (msgs != null && !msgs.isEmpty()) {
                        List<String> updatedMsgs = new ArrayList<>();
                        boolean messageDeleted = false;
                        for (String storedMsgStr : msgs) {
                            if (!StringUtils.hasText(storedMsgStr)) {
                                log.warn("Redis中发现空字符串消息，跳过处理：索引={}，delete操作", storedMsgStr);
                                continue;
                            }

                            String cleanedMsgStr = storedMsgStr.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "").trim();

                            if (!cleanedMsgStr.startsWith("{") && !cleanedMsgStr.startsWith("[")) {
                                log.warn("Redis中发现非JSON格式消息（旧格式或损坏，无法删除）：{}。delete操作", storedMsgStr);
                                updatedMsgs.add(storedMsgStr); // 非JSON消息保留
                                continue;
                            }

                            JSONObject storedMsg = null;
                            JSONObject content = null;
                            try {
                                storedMsg = JSONObject.parseObject(cleanedMsgStr);
                                if (storedMsg != null && storedMsg.containsKey("content")) {
                                    Object contentObj = storedMsg.get("content");
                                    if (contentObj instanceof JSONObject) {
                                        content = (JSONObject) contentObj;
                                    }
                                }

                                // 尝试匹配 timestamp，如果匹配成功，则不加入到 updatedMsgs 中（即删除）
                                if (content != null && deleteMsgId.equals(content.getString("timestamp"))) {
                                    log.info("消息删除成功：msgId={}, userId={}", deleteMsgId, deleteTargetUserId);
                                    messageDeleted = true;
                                    // 不将此消息添加到 updatedMsgs，实现删除
                                } else {
                                    updatedMsgs.add(storedMsgStr); // 不匹配的消息保留
                                }
                            } catch (Exception e) {
                                log.warn("解析缓存消息失败或消息格式不正确（即使清理后），无法删除：{}。delete操作", storedMsgStr, e);
                                updatedMsgs.add(storedMsgStr); // 解析失败的消息保留
                            }
                        }

                        // 如果有消息被删除或列表结构有变动，原子性地更新Redis列表
                        if (messageDeleted || updatedMsgs.size() != msgs.size()) {
                            redisTemplate.delete(redisKey); // 删除旧列表
                            if (!updatedMsgs.isEmpty()) {
                                redisTemplate.opsForList().rightPushAll(redisKey, updatedMsgs); // 写入新列表
                            }
                        } else {
                            log.info("用户[{}]的未读消息列表中未找到匹配消息，无需删除：msgId={}", deleteTargetUserId, deleteMsgId);
                        }
                    } else {
                        log.warn("未找到用户[{}]的未读消息列表，无需删除", deleteTargetUserId);
                    }
                } else {
                    log.warn("delete 类型消息缺少 timestamp 或 userId：timestamp={}, userId={}", deleteMsgId, deleteTargetUserId);
                }
                break;
            // --- delete 功能结束 ---

            default:
                session.sendMessage(new TextMessage("错误：未知的消息类型"));
                break;
        }
    }


    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessionMap.entrySet().removeIf(entry -> entry.getValue().equals(session));
        pondGroupMap.values().forEach(set -> set.removeIf(userId -> !sessionMap.containsKey(userId)));
        log.info("用户下线");
    }

    public void sendToUser(String userId, String message) {
        WebSocketSession session = sessionMap.get(userId);

        if (session != null && session.isOpen()) {
            log.info("尝试发送消息给在线用户[{}]：{}", userId, message);
            try {
                session.sendMessage(new TextMessage(message));
            } catch (Exception e) {
                log.error("发送私聊消息失败，userId=" + userId, e);
            }
        } else {
            log.warn("用户[{}]不在线，消息将仅缓存", userId);
        }

        // 无论是否在线或是否实时发送成功，都将消息缓存为 **未读状态**。
        // read 状态的改变只由客户端的 "read" 消息类型触发。
        // 对于 'admin' 类型消息，它在 notifyAdmins 处已经封装为 {"content": ..., "read": false}
        // 对于 'group'/'private' 消息，它们也在 handleTextMessage 处封装为 {"content": ..., "read": false}
        // 所以这里直接缓存传入的 message 即可。
        cacheUnreadMessage(userId, message);
    }

    /**
     * 缓存消息到 Redis。无论消息是否实时发送成功，都会以 `read: false` 状态存储。
     * 只有当客户端发送 "read" 消息类型时，`read` 状态才会被更新为 `true`。
     *
     * @param userId 目标用户ID
     * @param message 要缓存的原始消息字符串 (期望已是 {"content": ..., "read": false} 结构)
     */
    private void cacheUnreadMessage(String userId, String message) {
        try {
            String redisKey = "admin:unread:" + userId;

            // 缓存的消息期望已经是 {"content": {...},"read":false} 结构
            // 因此只需要清理一下字符串中的控制字符即可，无需重新包装
            String cleanedMessage = message.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "").trim();

            // 再次尝试解析并确保其格式正确，以防止外部调用者传递了非预期格式
            JSONObject msgWrapper;
            try {
                msgWrapper = JSONObject.parseObject(cleanedMessage);
                // 确保有content和read字段，如果没有则添加默认
                if (!msgWrapper.containsKey("content")) {
                    msgWrapper.put("content", cleanedMessage); // 兜底：如果没content，把整个消息作为content
                }
                // 强制设置为read: false，无论传入的message里read是什么
                msgWrapper.put("read", false);
            } catch (Exception e) {
                // 如果 cleanedMessage 不是有效的 JSON，则将其作为 content 封装
                log.warn("缓存消息时发现传入的消息不是有效的JSON格式，将以纯文本内容存储: {}", cleanedMessage, e);
                msgWrapper = new JSONObject();
                msgWrapper.put("content", cleanedMessage);
                msgWrapper.put("read", false);
            }

            redisTemplate.opsForList().rightPush(redisKey, msgWrapper.toString());
            log.info("消息缓存到Redis，key={}, message={}", redisKey, msgWrapper.toString());
        } catch (Exception e) {
            log.error("缓存未读消息失败，userId=" + userId, e);
        }
    }

    public void sendToGroup(String pondId, String message) {
        Set<String> users = pondGroupMap.getOrDefault(pondId, Collections.emptySet());
        for (String userId : users) {
            sendToUser(userId, message);
        }
    }

    public void notifyAdmins(String message) {
        try {
            R<List<Long>> result = sysUserClient.getAllAdminIds();
            if (result != null && result.getCode() == 200 && result.getData() != null) {
                for (Long adminId : result.getData()) {
                    String adminIdStr = String.valueOf(adminId);

                    // --- 关键修改：在这里封装消息格式 ---
                    JSONObject wrappedMessage = new JSONObject();
                    JSONObject originalContent;
                    try {
                        // 尝试解析原始消息，如果它已经是JSON
                        originalContent = JSONObject.parseObject(message);
                    } catch (Exception e) {
                        // 如果不是JSON，就把它作为字符串内容
                        log.warn("notifyAdmins接收到非JSON格式消息，将其作为content字符串封装: {}", message, e);
                        originalContent = new JSONObject();
                        originalContent.put("raw_content", message); // 或者根据实际情况处理
                    }
                    wrappedMessage.put("content", originalContent);
                    wrappedMessage.put("read", false); // 初始状态为未读

                    sendToUser(adminIdStr, wrappedMessage.toString()); // 传递封装后的消息
                }
            } else {
                log.warn("获取管理员ID列表失败或为空，result={}", result);
            }
        } catch (Exception e) {
            log.error("通知管理员时获取管理员列表异常", e);
        }
    }

    private Map<String, String> parseQueryParams(String query) {
        Map<String, String> map = new HashMap<>();
        if (query != null) {
            for (String pair : query.split("&")) {
                String[] kv = pair.split("=", 2);
                if (kv.length == 2) {
                    map.put(kv[0], kv[1]);
                }
            }
        }
        return map;
    }

    /**
     * 发送未读消息给管理员。现在会发送所有消息，前端根据read字段判断是否已读。
     * 关键改进：在发送前，对从 Redis 读取的 message 字符串进行清理和标准化。
     */
    public void sendUnreadMessagesToAdmin(String adminId) {
        String redisKey = "admin:unread:" + adminId;
        List<String> unreadMessages = redisTemplate.opsForList().range(redisKey, 0, -1);
        if (unreadMessages != null && !unreadMessages.isEmpty()) {
            for (String message : unreadMessages) {
                // 从Redis取出的消息，我们期望它已经是 {"content": {...}, "read": false/true} 结构
                // 因此这里主要做的是清理和安全解析，然后直接发送其内容
                String cleanedMsg = message.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "").trim();

                JSONObject msgToSend;
                try {
                    msgToSend = JSONObject.parseObject(cleanedMsg);
                    // 再次检查结构，确保即使缓存中出现问题也能有兜底
                    if (!msgToSend.containsKey("content") || !msgToSend.containsKey("read")) {
                        log.warn("从Redis读取的消息格式不完整，强制修复以包含content和read字段: {}", cleanedMsg);
                        JSONObject tempContent = new JSONObject();
                        tempContent.put("raw_data", cleanedMsg); // 将原始清理后的字符串作为内容
                        msgToSend.put("content", tempContent);
                        msgToSend.put("read", false); // 默认未读
                    }
                } catch (Exception e) {
                    log.warn("从Redis读取消息时解析失败，将以原始字符串内容发送: {}", cleanedMsg, e);
                    msgToSend = new JSONObject();
                    msgToSend.put("content", cleanedMsg);
                    msgToSend.put("read", false);
                }

                WebSocketSession session = sessionMap.get(adminId);
                if (session != null && session.isOpen()) {
                    try {
                        session.sendMessage(new TextMessage(msgToSend.toString()));
                        log.info("向管理员[{}]发送已缓存消息: {}", adminId, msgToSend.toString());
                    } catch (IOException e) {
                        log.error("向管理员[{}]发送已缓存消息失败: {}", adminId, e.getMessage());
                    }
                } else {
                    log.warn("尝试发送缓存消息时管理员[{}]不在线或session已关闭", adminId);
                }
            }
        }
    }

    public void sendToAdmins(String message) {
        try {
            R<List<Long>> result = sysUserClient.getAllAdminIds();
            if (result != null && result.getCode() == 200 && result.getData() != null) {
                for (Long adminId : result.getData()) {
                    // --- 关键修改：在这里封装消息格式 ---
                    JSONObject wrappedMessage = new JSONObject();
                    JSONObject originalContent;
                    try {
                        // 尝试解析原始消息，如果它已经是JSON
                        originalContent = JSONObject.parseObject(message);
                    } catch (Exception e) {
                        // 如果不是JSON，就把它作为字符串内容
                        log.warn("sendToAdmins接收到非JSON格式消息，将其作为content字符串封装: {}", message, e);
                        originalContent = new JSONObject();
                        originalContent.put("raw_content", message); // 或者根据实际情况处理
                    }
                    wrappedMessage.put("content", originalContent);
                    wrappedMessage.put("read", false); // 初始状态为未读

                    sendToUser(String.valueOf(adminId), wrappedMessage.toString()); // 传递封装后的消息
                }
            } else {
                log.warn("获取管理员ID列表失败或为空，result={}", result);
            }
        } catch (Exception e) {
            log.error("发送给管理员消息时获取管理员列表异常", e);
        }
    }
}